import math
import time
from typing import Any, Dict

import numpy as np
import pandas as pd
import sympy
from scipy.optimize import root

from app.models.TimeSequenceModel import TimeSequenceModel


class GOMethod:
    def __init__(self, failure_dataframe, precision):
        self.L_a = None
        self.L_b = None
        self.dL_a_da = None
        self.dL_b_da = None
        self.dL_b_db = None
        self.dL_a_db = None
        self.__para = {"a": None, "b": None}
        self.__fail = failure_dataframe["value"].tolist()
        self.__diff_fail = TimeSequenceModel \
            .differ_fail(failure_dataframe)["value"].tolist()
        self.__precision = precision

    @staticmethod
    def get_para(a, b) -> Dict[str, Any]:
        return {"a": a, "b": b}

    @staticmethod
    def get_numerical_m(para, x) -> float:
        a = para["a"]
        b = para["b"]
        return a * (1 - math.exp(-b * x))

    @staticmethod
    def get_numerical_R(para, t, dt) -> float:
        a = para["a"]
        b = para["b"]
        return math.exp(a * (math.exp(-b(t + dt)) - math.exp(-b * t)))

    @staticmethod
    def get_numerical_z(para, t, dt) -> float:
        a = para["a"]
        b = para["b"]
        return a * b * math.exp(-b * (t + dt))

    @staticmethod
    def get_numerical_f(para, t, dt) -> float:
        a = para["a"]
        b = para["b"]
        R = GOMethod.get_numerical_R(para, t, dt)
        return a * b * R * math.exp(-b * (t + dt))

    @staticmethod
    def get_numerical_F(para, t, dt) -> float:
        return 1 - GOMethod.get_numerical_R(para, t, dt)

    @staticmethod
    def get_symbol_R() -> Any:
        a, b, t, dt = sympy.symbols("a b t dt", positive=True)
        return sympy.exp(a * (sympy.exp(-b * (t + dt)) - sympy.exp(-b * t)))

    @staticmethod
    def get_symbol_F() -> Any:
        return 1 - GOMethod.get_symbol_F()

    @staticmethod
    def get_symbol_f() -> Any:
        a, b, t, dt = sympy.symbols("a b t dt", positive=True)
        return a * b * GOMethod.get_symbol_R() * sympy.exp(-b * (t + dt))

    @staticmethod
    def loading(drop_frame) -> Any:
        time.sleep(drop_frame)

    def get_MLE(self) -> Any:
        a, b, t, dt = sympy.symbols("a b t dt", positive=True)
        fail_list = self.__fail[0: 5]
        diff_fail_list = self.__diff_fail[0: 5]

        L_list = []
        for item in fail_list:
            idx = fail_list.index(item)
            L_list.append(self.get_symbol_f().subs({t: item, dt: diff_fail_list[idx]}))

        L = np.prod(L_list)

        self.L_a = sympy.diff(sympy.expand_log(sympy.log(L)), a)
        self.L_b = sympy.diff(sympy.expand_log(sympy.log(L)), b)
        self.dL_a_da = sympy.diff(self.L_a, a)
        self.dL_a_db = sympy.diff(self.L_a, b)
        self.dL_b_da = sympy.diff(self.L_a, a)
        self.dL_b_db = sympy.diff(self.L_a, b)

    def func_index(self, x):
        a, b = sympy.symbols("a, b", position=True)
        para = {a: x[0], b: x[1]}
        func = [self.L_a.subs(para), self.L_b.subs(para)]
        differ_func = [[self.dL_a_da.subs(para), self.dL_a_db.subs(para)],
                       [self.dL_b_da.subs(para), self.dL_b_db.subs(para)]]
        return func, differ_func

    def get_para_estimate(self):
        sol = root(self.func_index, [1, 1], jac=True, method="lm")


if __name__ == "__main__":
    df = pd.DataFrame({"value": [1, 2, 3, 5, 5]})
